package dir_sync;

import javax.swing.*;
import java.io.File;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Vector;

import static javax.swing.SwingUtilities.invokeLater;

/**
 * 文件及目录迭代比较器。 比较两个目录的结构是否相同，列出不同的文件及目录的数组。
 */
public class FileCompare {

    /**
     * 源目录Directory （比较目录）
     */
    private final File srcBaseDir;
    /**
     * 目标目录Directory （被比较目录）
     */
    private final File dstBaseDir;

    private final DefaultListModel<String> listModel;
    private int differentCount;
    private final boolean onlyShowDiff;
    private final int scanDeep;
    private final boolean hasScanDeep;
    private final JLabel statusLabel;
    private final Vector<File> srcChangedFiles;

    /**
     * FileCompare 构造器
     *
     * @param listModel       显示结果的JList
     * @param srcDir          源目录Directory （比较目录）
     * @param dstDir          目标目录Directory （被比较目录）
     * @param scanDeep        int
     * @param statusLabel     JLabel
     * @param srcChangedFiles Vector<File>
     */
    public FileCompare(DefaultListModel<String> listModel, File srcDir, File dstDir, boolean onlyShowDiff, int scanDeep,
                       JLabel statusLabel, Vector<File> srcChangedFiles) {
        this.srcBaseDir = srcDir;
        this.dstBaseDir = dstDir;
        this.listModel = listModel;
        this.differentCount = 0;
        this.onlyShowDiff = onlyShowDiff;
        this.scanDeep = scanDeep;
        this.hasScanDeep = scanDeep > 0;
        this.statusLabel = statusLabel;
        this.srcChangedFiles = srcChangedFiles;
    }


    /**
     * 目录比较的核心方法
     */
    private void compare(File srcFile, File dstFile) throws InterruptedException {
        if (srcFile == null || dstFile == null) {
            return;
        }

        // 使用双端队列进行非递归遍历
        Queue<FilePair> queue = new LinkedList<>();
        queue.offer(new FilePair(srcFile, dstFile, this.scanDeep));

        while (!queue.isEmpty()) {
            if (Thread.currentThread().isInterrupted() || !DirCompare.isComparing) {
                throw new InterruptedException("目录比较被中断");
            }
            FilePair pair = queue.poll();
            if (pair.src != null) {
                this.statusLabel.setText("正在比较：" + pair.src.getAbsolutePath());
            }

            if (hasScanDeep) {
                if (pair.deep <= 0) {
                    break;
                } else {
                    pair.deep = pair.deep - 1;
                }
            }

            processDirectory(pair, queue);
        }
    }

    private void processDirectory(FilePair pair, Queue<FilePair> queue) throws InterruptedException {
        if (Thread.currentThread().isInterrupted()) {
            throw new InterruptedException("目录比较被中断");
        }
        File src = pair.src;
        File dst = pair.dst;

        File[] arr_a = src.listFiles();
        File[] arr_b = dst.listFiles();

        if (arr_a == null) {
            arr_a = new File[0];
        }
        if (arr_b == null) {
            arr_b = new File[0];
        }

        if (arr_a.length == 0 && arr_b.length == 0) {
            return;
        }

        if (arr_b.length == 0) {
            for (File file : arr_a) {
                srcChangedFiles.add(file);
                addDifference(file.getAbsolutePath() + "--> 不存在");
            }
            return;
        }

        for (File srcFileItem : arr_a) {
            if (Thread.currentThread().isInterrupted() || !DirCompare.isComparing) {
                throw new InterruptedException("目录比较被中断");
            }
            String fn = srcFileItem.getName();
            if (BlackList.isBlack(fn)) {
                System.out.println("忽略文件：" + srcFileItem.getName());
                continue;
            }
            if (srcFileItem.isDirectory()) {
                handleDirectory(srcFileItem, dst, queue, pair.deep);
            } else {
                checkDstFileDiff(srcFileItem);
            }
        }
    }

    private void handleDirectory(File srcDir, File dst, Queue<FilePair> queue, int currentDeep) {
        File correspondingDstDir = new File(dst, srcDir.getName());
        if (correspondingDstDir.exists() && correspondingDstDir.isDirectory()) {

            // 目录的修改时间比较，没有意义

            if (!this.onlyShowDiff) {
                invokeLater(() -> this.listModel.addElement(srcDir.getAbsolutePath() + "--> 存在"));
            }
            queue.offer(new FilePair(srcDir, correspondingDstDir, currentDeep));
        } else {
            srcChangedFiles.add(srcDir);
            addDifference(srcDir.getAbsolutePath() + "--> 不存在 --> " + correspondingDstDir.getAbsolutePath());
        }
    }

    private void addDifference(String message) {
        this.differentCount++;
        invokeLater(() -> this.listModel.addElement(message));
    }

    // 辅助类用于存储文件对
    private static class FilePair {
        File src;
        File dst;
        int deep;

        FilePair(File src, File dst, int deep) {
            this.src = src;
            this.dst = dst;
            this.deep = deep;
        }
    }

    private void checkDstFileDiff(File src) {
        String sp = src.getAbsolutePath();

        String base = this.srcBaseDir.getAbsolutePath();
        // 如果路径结尾带有路径分隔符，则不加1
        String srcPath;
        if (base.endsWith(File.separator)) {
            srcPath = sp.substring(base.length());
        } else {
            srcPath = sp.substring(base.length() + 1);
        }

        // 有些文件前后存在特殊不可见字符，如果加上windows判断成不存在.
        String partFilePath;

        int index = srcPath.lastIndexOf(File.separator);
        if (index == -1 || srcPath.length() < index + 1) {
            partFilePath = srcPath;
        } else {
            String fileName = srcPath.substring(srcPath.lastIndexOf(File.separator) + 1);
            fileName = removeLeftChineseSpace(fileName);
            String prefix = srcPath.substring(0, srcPath.lastIndexOf(File.separator));

            partFilePath = prefix + File.separator + fileName;
        }

        String dstPath = dstBaseDir.getAbsolutePath() + File.separator + partFilePath;


        // TODO 通过MD5比较内容，但会非常慢.

        File dstFile = new File(dstPath);
        if (!dstFile.exists()) {
            this.differentCount++;
            srcChangedFiles.add(src);
            invokeLater(() -> this.listModel.addElement(src.getAbsolutePath() + "--> 不存在 --> " + dstPath));
        } else if (src.length() != dstFile.length()) {
            srcChangedFiles.add(src);
            this.differentCount++;
            invokeLater(() -> this.listModel.addElement(src.getAbsolutePath() + "--> 文件大小不同"));
        } else {
            if (!this.onlyShowDiff) {
                invokeLater(() -> this.listModel.addElement(src.getAbsolutePath() + "--> 相同"));
            }
        }
    }

    public int call() throws Exception {
        // 线程结束后直接返回比较结果集（一个包括比较出不同的文件数组）
        this.differentCount = 0;
        compare(this.srcBaseDir, this.dstBaseDir);
        invokeLater(() -> this.listModel.addElement("发现原始目录中有" + this.differentCount + "处不同"));
        return this.differentCount;
    }

    // 去掉文件名称左侧的不可见字符，不可见字符不仅是空格
    private static String removeLeftChineseSpace(String fileName) {
        // 只去掉文件名左侧的不间断空格字符（UTF-8 编码为 CE A0）
        if (fileName.startsWith("\u00A0")) {
            return fileName.substring(1);
        }
        return fileName;
    }

}
